﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Microsoft.Xna.Framework;

namespace Pavilion.GameTools
{
    public enum ConnectionAction { Connect, Disconnect } 
    public enum ConnectionStatus { Connected, Disconnected, Connecting, Disconnecting }
    public enum ConnectionInitiator { Initiator, NotInitiator, None }
    public enum ConnectionType { Rider, Mover }

    public class ConnectionTool : GameToolBase
    {
        private static Dictionary<string, Action<ConnectionTool, object>> dataExecutionDictionary;

        public ConnectionStatus ConnectionStatus { get { return connectionStatus; } private set { SetConnectionStatus(value); } }
        public ConnectionType ConnectionType { get { return connectionType; } set { connectionType = value; } }
        public ConnectionInitiator ConnectionInitiator { get { return isInitiator; } private set { isInitiator = value; OnPropertyChanged("ConnectionInitiator"); } }

        public ConnectionAction Action { set { SetAction(value); } }
        public float Speed { get { return speed; } set { SetSpeed(value); } }

        private ConnectionStatus connectionStatus;
        private ConnectionInitiator isInitiator;
        private ConnectionType connectionType;
        private ConnectionManager connectionManager;
        private float speed;

        public ConnectionTool(GameItem parentGameItem)
            : base(parentGameItem)
        {
            ConnectionStatus = ConnectionStatus.Disconnected;
            Speed = 2;

            LoadDataExecution();
        }

        private void LoadDataExecution()
        {
            if (dataExecutionDictionary != null)
                return;

            dataExecutionDictionary = new Dictionary<string, Action<ConnectionTool, object>>();

            dataExecutionDictionary.Add("Action", (connectionTool, value) => connectionTool.Action = (ConnectionAction)value);
            dataExecutionDictionary.Add("Speed", (connectionTool, value) => connectionTool.Speed = (float)value);
            dataExecutionDictionary.Add("ConnectionType", (connectionTool, value) => connectionTool.connectionType = (ConnectionType)value);
        }

        public override void UpdateProperty(string propertyName, object propertyValue)
        {
            dataExecutionDictionary[propertyName].Invoke(this, propertyValue);
        }

        public void Connect()
        {
            if (connectionStatus != ConnectionStatus.Disconnected)
                return;

            ConnectionTool connectionTool = ObtainConnectableItem();

            if (connectionTool != null)
            {
                ConnectionInitiator = ConnectionInitiator.Initiator;

                connectionManager = connectionTool.RequestConnection(this);
                connectionManager.UpdateSpeed(Speed);
            }
        }

        public void Disconnect()
        {
            if (connectionManager == null || (ConnectionStatus != ConnectionStatus.Connected && ConnectionStatus != ConnectionStatus.Connecting))
                return;

            connectionManager.Dispose();
            connectionManager = null;
        }

        private ConnectionManager RequestConnection(ConnectionTool connectionTool)
        {
            if (connectionTool.ConnectionType == this.ConnectionType)
                return null;

            ConnectionInitiator = ConnectionInitiator.NotInitiator;
            connectionManager = new ConnectionManager(this, connectionTool);

            return connectionManager;
        }

        private ConnectionTool ObtainConnectableItem()
        {
            PolygonCollisionData collisionData = CollisionManager.Instance.GetPolygonToPolygonsData(ParentGameItem.ObtainTool<PhysicalTypeTool>().PolygonData);

            if (collisionData.IntersectedPolygons.Count == 0)
                return null;

            GameItem gameItem = collisionData.IntersectedPolygons[0].ParentGameTool.ParentGameItem;

            if (IsGameItemConnectable(gameItem))
                return gameItem.ObtainTool<ConnectionTool>();

            return null;
        }

        private bool IsGameItemConnectable(GameItem gameItem)
        {
            return gameItem.ContainsTool<ConnectionTool>();
        }

        private void SetConnectionStatus(ConnectionStatus value)
        {
            if (connectionStatus == value)
                return;

            connectionStatus = value;

            if (ConnectionStatus == ConnectionStatus.Disconnected)
            {
                connectionManager = null;
                ConnectionInitiator = ConnectionInitiator.None;
            }

            OnPropertyChanged("ConnectionStatus");
        }

        private void SetSpeed(float value)
        {
            if (value <= 0)
                throw new InvalidOperationException("Speed value can less then or equal to zero");

            speed = value;

            if (connectionManager != null)
                connectionManager.UpdateSpeed(speed);
        }

        private void SetAction(ConnectionAction value)
        {
            if (value == ConnectionAction.Connect)
                Connect();
            else if (value == ConnectionAction.Disconnect)
                Disconnect();
        }

        private class ConnectionManager
        {
            private PolygonPhysicalData riderPolygon, moverPolygon;
            private ConnectionTool riderConnection, moverConnection;
            private PolygonConnectionMerger merger;

            public ConnectionManager(ConnectionTool connectionA, ConnectionTool connectionB)
            {
                if (connectionA.ConnectionType == ConnectionType.Rider)
                {
                    this.riderConnection = connectionA;
                    this.moverConnection = connectionB;
                }
                else
                {
                    this.riderConnection = connectionB;
                    this.moverConnection = connectionA;
                }

                this.riderPolygon = riderConnection.ParentGameItem.ObtainTool<PhysicalTypeTool>().PolygonData;
                this.moverPolygon = moverConnection.ParentGameItem.ObtainTool<PhysicalTypeTool>().PolygonData;

                SetConnectionStatus(ConnectionStatus.Connecting);

                if (riderConnection.ConnectionInitiator == ConnectionInitiator.Initiator)
                    merger = new PolygonConnectionMerger(moverPolygon, riderPolygon, riderConnection.ParentGameItem);
                else
                    merger = new PolygonConnectionMerger(riderPolygon, moverPolygon, moverConnection.ParentGameItem);

                riderPolygon.IsFlippedHorizontally = moverPolygon.IsFlippedHorizontally;
                merger.BeginConnecting();
                merger.Connected += () =>
                {
                    LoadRider();
                    SetConnectionStatus(ConnectionStatus.Connected);
                    merger = null;
                };
            }

            public void UpdateSpeed(float speed)
            {
                if (merger != null)
                    merger.UpdateSpeedProperties(speed);
            }

            public void SetConnectionStatus(ConnectionStatus connectionStatus)
            {
                moverConnection.ConnectionStatus = riderConnection.ConnectionStatus = connectionStatus;
            }

            public void Dispose()
            {
                if (riderConnection.ConnectionStatus == ConnectionStatus.Connected)
                    UnloadRider();

                SetConnectionStatus(ConnectionStatus.Disconnecting);

                if (merger != null)
                {

                    merger.BeginDisconnecting();
                    merger = null;
                }

                SetConnectionStatus(ConnectionStatus.Disconnected);
            }

            public ConnectionTool ObtainConnector(ConnectionTool selfConnector)
            {
                if (selfConnector == moverConnection)
                    return riderConnection;
                else
                    return moverConnection;
            }

            private void LoadRider()
            {
                riderPolygon.IsFlippedHorizontally = moverPolygon.IsFlippedHorizontally;
                moverPolygon.PositionChanged += MoverPolygon_PositionChanged;
                moverPolygon.IsFlippedHorizontallyChanged += new Action<PhysicalData>(MoverPolygon_IsFlippedHorizontallyChanged);
            }

            private void UnloadRider()
            {
                moverPolygon.PositionChanged -= MoverPolygon_PositionChanged;
                moverPolygon.IsFlippedHorizontallyChanged -= new Action<PhysicalData>(MoverPolygon_IsFlippedHorizontallyChanged);
            }

            private void MoverPolygon_PositionChanged(PhysicalData physicalData)
            {
                riderPolygon.SetPosition(physicalData.Position.X, physicalData.Position.Y);
            }

            private void MoverPolygon_IsFlippedHorizontallyChanged(PhysicalData physicalData)
            {
                riderPolygon.IsFlippedHorizontally = physicalData.IsFlippedHorizontally;
            }

            private class PolygonConnectionMerger
            {
                public event Action Connected;

                private GameItem gameItem;
                private PolygonPhysicalData stationaryPolygon, movingPolygon;
                private Vector2 positionDifference, positionDelta;
                private Func<bool> isConnectedAction;
                private bool isConnecting;

                public PolygonConnectionMerger(PolygonPhysicalData stationaryPolygon, PolygonPhysicalData movingPolygon, GameItem gameItem)
                {
                    this.stationaryPolygon = stationaryPolygon;
                    this.movingPolygon = movingPolygon;
                    this.gameItem = gameItem;
                }

                public void BeginConnecting()
                {
                    isConnecting = true;
                    gameItem.UpdateLogic += new Action<GameTime>(GameItem_UpdateLogic);

                    UpdateSpeedProperties(2);
                }

                public void BeginDisconnecting()
                {
                    if (isConnecting)
                    {
                        gameItem.UpdateLogic -= new Action<GameTime>(GameItem_UpdateLogic);
                        isConnecting = false;
                    }
                }

                public void UpdateSpeedProperties(float speed)
                {
                    if (!isConnecting)
                        return;

                    if (stationaryPolygon.Position.X == movingPolygon.Position.X)
                        movingPolygon.Move(0.1f, 0);

                    positionDifference = stationaryPolygon.Position - movingPolygon.Position;
                    positionDelta = positionDifference * speed;

                    if (positionDifference.X < 0)
                        isConnectedAction = () => positionDifference.X >= 0;
                    else
                        isConnectedAction = () => positionDifference.X <= 0;
                }

                private void GameItem_UpdateLogic(GameTime gameTime)
                {
                    if (!isConnecting)
                        return;

                    positionDifference -= positionDelta * (float)gameTime.ElapsedGameTime.TotalSeconds;
                    movingPolygon.SetPosition(stationaryPolygon.Position - positionDifference);

                    if (isConnectedAction.Invoke())
                    {
                        isConnecting = false;
                        movingPolygon.SetPosition(stationaryPolygon.Position);
                        gameItem.UpdateLogic -= new Action<GameTime>(GameItem_UpdateLogic);
                        OnConnected();
                    }
                }

                public void OnConnected()
                {
                    if (Connected != null)
                        Connected();
                }
            }
        }
    }
}
